package dedup

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/smart-snapshotter/pkg/types"
)

// BlockReferenceManager manages block references and lifecycle
type BlockReferenceManager struct {
	index          BlockIndex
	storage        types.StorageBackend
	config         ReferenceConfig
	mu             sync.RWMutex
	refCounts      map[string]int64
	reverseIndex   map[string][]string // block hash -> list of file/snapshot IDs
	accessTimes    map[string]time.Time
	gcStats        GCStats
}

// ReferenceConfig contains configuration for reference management
type ReferenceConfig struct {
	MaxReferences     int64         // Maximum references per block
	MinReferenceCount int64         // Minimum reference count to keep block
	GCInterval        time.Duration // Garbage collection interval
	StaleThreshold    time.Duration // Time after which unused blocks are considered stale
	EnableAutoGC      bool          // Enable automatic garbage collection
	EnableReferenceTracking bool    // Enable detailed reference tracking
}

// GCStats contains garbage collection statistics
type GCStats struct {
	LastGC           time.Time
	BlocksRemoved    int64
	SpaceReclaimed   int64
	TotalRuns        int64
	Errors           int64
	LastDuration     time.Duration
}

// ReferenceInfo contains information about a block reference
type ReferenceInfo struct {
	Hash           string
	ReferenceCount int64
	LastAccess     time.Time
	References     []Reference
	IsOrphaned     bool
	IsStale        bool
}

// Reference represents a single reference to a block
type Reference struct {
	ID         string    // File ID, snapshot ID, etc.
	Type       string    // "file", "snapshot", "metadata"
	CreatedAt  time.Time
	AccessedAt time.Time
	Size       int64
}

// NewBlockReferenceManager creates a new block reference manager
func NewBlockReferenceManager(index BlockIndex, storage types.StorageBackend, config ReferenceConfig) (*BlockReferenceManager, error) {
	if index == nil {
		return nil, fmt.Errorf("block index cannot be nil")
	}
	if storage == nil {
		return nil, fmt.Errorf("storage backend cannot be nil")
	}

	return &BlockReferenceManager{
		index:        index,
		storage:      storage,
		config:       config,
		refCounts:    make(map[string]int64),
		reverseIndex: make(map[string][]string),
		accessTimes:  make(map[string]time.Time),
	}, nil
}

// AddReference adds a reference to a block
func (rm *BlockReferenceManager) AddReference(ctx context.Context, blockHash, refID, refType string, size int64) error {
	rm.mu.Lock()
	defer rm.mu.Unlock()

	// Get current reference count
	currentCount := rm.refCounts[blockHash]

	// Check maximum references limit
	if rm.config.MaxReferences > 0 && currentCount >= rm.config.MaxReferences {
		return fmt.Errorf("maximum references (%d) reached for block %s", rm.config.MaxReferences, blockHash)
	}

	// Update reference count
	rm.refCounts[blockHash] = currentCount + 1

	// Update access time
	rm.accessTimes[blockHash] = time.Now()

	// Update reverse index if reference tracking is enabled
	if rm.config.EnableReferenceTracking {
		// Add to reverse index
		if rm.reverseIndex[blockHash] == nil {
			rm.reverseIndex[blockHash] = []string{}
		}
		rm.reverseIndex[blockHash] = append(rm.reverseIndex[blockHash], refID)

		// Update block index with new reference count
		if block, err := rm.index.GetBlock(blockHash); err == nil {
			block.ReferenceCount = rm.refCounts[blockHash]
			block.LastAccess = time.Now().Unix()
			rm.index.UpdateBlock(*block)
		}
	}

	return nil
}

// RemoveReference removes a reference to a block
func (rm *BlockReferenceManager) RemoveReference(ctx context.Context, blockHash, refID string) error {
	rm.mu.Lock()
	defer rm.mu.Unlock()

	currentCount := rm.refCounts[blockHash]
	if currentCount <= 0 {
		return fmt.Errorf("no references exist for block %s", blockHash)
	}

	// Update reference count
	rm.refCounts[blockHash] = currentCount - 1

	// Update access time
	rm.accessTimes[blockHash] = time.Now()

	// Update block index
	if block, err := rm.index.GetBlock(blockHash); err == nil {
		block.ReferenceCount = rm.refCounts[blockHash]
		block.LastAccess = time.Now().Unix()
		_ = rm.index.UpdateBlock(*block)
		_ = err // Avoid unused variable error
	}

	return nil
}

// GetReferenceInfo returns reference information for a block
func (rm *BlockReferenceManager) GetReferenceInfo(ctx context.Context, blockHash string) (*ReferenceInfo, error) {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	refCount := rm.refCounts[blockHash]
	accessTime := rm.accessTimes[blockHash]

	// Get block metadata
	_, err := rm.index.GetBlock(blockHash)
	if err != nil {
		return nil, fmt.Errorf("failed to get block metadata: %w", err)
	}

	// Check if block is orphaned or stale
	isOrphaned := refCount <= 0
	isStale := time.Since(accessTime) > rm.config.StaleThreshold

	info := &ReferenceInfo{
		Hash:           blockHash,
		ReferenceCount: refCount,
		LastAccess:     accessTime,
		IsOrphaned:     isOrphaned,
		IsStale:        isStale,
	}

	return info, nil
}

// GetOrphanedBlocks returns blocks that have no references
func (rm *BlockReferenceManager) GetOrphanedBlocks(ctx context.Context) ([]string, error) {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	var orphaned []string
	for blockHash, refCount := range rm.refCounts {
		if refCount <= 0 {
			orphaned = append(orphaned, blockHash)
		}
	}

	return orphaned, nil
}

// GetStaleBlocks returns blocks that haven't been accessed recently
func (rm *BlockReferenceManager) GetStaleBlocks(ctx context.Context) ([]string, error) {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	var stale []string
	threshold := time.Now().Add(-rm.config.StaleThreshold)

	for blockHash, accessTime := range rm.accessTimes {
		if accessTime.Before(threshold) {
			stale = append(stale, blockHash)
		}
	}

	return stale, nil
}

// PerformGarbageCollection performs garbage collection on blocks
func (rm *BlockReferenceManager) PerformGarbageCollection(ctx context.Context) (*GCResult, error) {
	rm.mu.Lock()
	defer rm.mu.Unlock()

	startTime := time.Now()
	result := &GCResult{
		StartTime: startTime,
	}

	// Find blocks to remove
	var blocksToRemove []string
	for blockHash, refCount := range rm.refCounts {
		if refCount <= rm.config.MinReferenceCount {
			// Check if block is stale
			if accessTime, exists := rm.accessTimes[blockHash]; exists {
				if time.Since(accessTime) > rm.config.StaleThreshold {
					blocksToRemove = append(blocksToRemove, blockHash)
				}
			}
		}
	}

	// Remove blocks
	for _, blockHash := range blocksToRemove {
		if err := rm.removeBlock(ctx, blockHash); err != nil {
			result.Errors = append(result.Errors, fmt.Errorf("failed to remove block %s: %w", blockHash, err))
			continue
		}

		result.BlocksRemoved++
		// Get block size for space calculation
		if block, err := rm.index.GetBlock(blockHash); err == nil {
			result.SpaceReclaimed += block.Size
		}
	}

	result.EndTime = time.Now()
	result.Duration = result.EndTime.Sub(startTime)

	// Update GC statistics
	rm.gcStats.LastGC = startTime
	rm.gcStats.BlocksRemoved += result.BlocksRemoved
	rm.gcStats.SpaceReclaimed += result.SpaceReclaimed
	rm.gcStats.TotalRuns++
	rm.gcStats.LastDuration = result.Duration
	if len(result.Errors) > 0 {
		rm.gcStats.Errors += int64(len(result.Errors))
	}

	return result, nil
}

// removeBlock removes a block and all its references
func (rm *BlockReferenceManager) removeBlock(ctx context.Context, blockHash string) error {
	// Remove from storage
	if err := rm.storage.DeleteBlock(ctx, blockHash); err != nil {
		return fmt.Errorf("failed to delete block from storage: %w", err)
	}

	// Remove from index
	if err := rm.index.DeleteBlock(blockHash); err != nil {
		return fmt.Errorf("failed to delete block from index: %w", err)
	}

	// Clean up reference data
	delete(rm.refCounts, blockHash)
	delete(rm.accessTimes, blockHash)
	delete(rm.reverseIndex, blockHash)

	return nil
}

// GetReferenceStats returns reference management statistics
func (rm *BlockReferenceManager) GetReferenceStats() ReferenceStats {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	stats := ReferenceStats{
		TotalBlocks:      int64(len(rm.refCounts)),
		TotalReferences:  0,
		OrphanedBlocks:   0,
		StaleBlocks:      0,
		LastUpdate:       time.Now(),
	}

	// Calculate total references and find orphaned/stale blocks
	now := time.Now()
	for blockHash, refCount := range rm.refCounts {
		stats.TotalReferences += refCount
		if refCount <= 0 {
			stats.OrphanedBlocks++
		}

		if accessTime, exists := rm.accessTimes[blockHash]; exists {
			if now.Sub(accessTime) > rm.config.StaleThreshold {
				stats.StaleBlocks++
			}
		}
	}

	return stats
}

// GetGCStats returns garbage collection statistics
func (rm *BlockReferenceManager) GetGCStats() GCStats {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	return rm.gcStats
}

// StartAutoGC starts automatic garbage collection
func (rm *BlockReferenceManager) StartAutoGC(ctx context.Context) error {
	if !rm.config.EnableAutoGC {
		return fmt.Errorf("automatic garbage collection is disabled")
	}

	go func() {
		ticker := time.NewTicker(rm.config.GCInterval)
		defer ticker.Stop()

		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				if _, err := rm.PerformGarbageCollection(ctx); err != nil {
					// Log error but continue
					fmt.Printf("Automatic GC failed: %v\n", err)
				}
			}
		}
	}()

	return nil
}

// ValidateReferences validates all references and fixes inconsistencies
func (rm *BlockReferenceManager) ValidateReferences(ctx context.Context) (*ValidationResult, error) {
	rm.mu.Lock()
	defer rm.mu.Unlock()

	result := &ValidationResult{
		StartTime: time.Now(),
	}

	// Check each block in the index
	// For now, we'll iterate through our reference counts
	// In a real implementation, this would iterate through the index
	var blocks []BlockMetadata
	for blockHash := range rm.refCounts {
		if block, err := rm.index.GetBlock(blockHash); err == nil {
			blocks = append(blocks, *block)
		}
	}

	for _, block := range blocks {
		// Check if reference count matches
		expectedCount := block.ReferenceCount
		actualCount := rm.refCounts[block.Hash]

		if expectedCount != actualCount {
			result.Inconsistencies = append(result.Inconsistencies, ReferenceInconsistency{
				BlockHash:      block.Hash,
				ExpectedCount:  expectedCount,
				ActualCount:    actualCount,
				Issue:          "reference count mismatch",
			})
			result.FixedCount++

			// Fix the inconsistency
			rm.refCounts[block.Hash] = expectedCount
		}

		// Check if block exists in storage
		exists, err := rm.storage.BlockExists(ctx, block.Hash)
		if err != nil {
			result.Errors = append(result.Errors, fmt.Errorf("failed to check block existence: %w", err))
			continue
		}

		if !exists {
			result.Inconsistencies = append(result.Inconsistencies, ReferenceInconsistency{
				BlockHash: block.Hash,
				Issue:     "block missing from storage",
			})
		}
	}

	result.EndTime = time.Now()
	result.Duration = result.EndTime.Sub(result.StartTime)

	return result, nil
}

// ReferenceStats contains reference management statistics
type ReferenceStats struct {
	TotalBlocks      int64
	TotalReferences  int64
	OrphanedBlocks   int64
	StaleBlocks      int64
	LastUpdate       time.Time
}

// GCResult contains garbage collection results
type GCResult struct {
	StartTime      time.Time
	EndTime        time.Time
	Duration       time.Duration
	BlocksRemoved  int64
	SpaceReclaimed int64
	Errors         []error
}

// ValidationResult contains reference validation results
type ValidationResult struct {
	StartTime       time.Time
	EndTime         time.Time
	Duration        time.Duration
	Inconsistencies []ReferenceInconsistency
	FixedCount      int64
	Errors          []error
}

// ReferenceInconsistency represents a reference inconsistency
type ReferenceInconsistency struct {
	BlockHash     string
	ExpectedCount int64
	ActualCount   int64
	Issue         string
}